/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "r_local.h"


static vertexBuffer_t *	r_vertexBuffersList;

static vertexBuffer_t	r_activeVertexBuffer;
static vertexBuffer_t *	r_freeVertexBuffer;


/*
 ==================
 R_AllocVertexBuffer
 ==================
*/
vertexBuffer_t *R_AllocVertexBuffer (bool dynamic, const void *data, int size){

	vertexBuffer_t	*vertexBuffer;

	if (!r_vertexBuffers->integerValue)
		return NULL;	// Not using vertex buffers

	if (!r_freeVertexBuffer)
		return NULL;	// No free slots

	vertexBuffer = r_freeVertexBuffer;

	// Fill it in
	vertexBuffer->dynamic = dynamic;
	vertexBuffer->size = size;
	vertexBuffer->frameCount = 0;

	// Bind the buffer
	qglGenBuffers(1, &vertexBuffer->bufferId);

	GL_BindVertexBuffer(vertexBuffer);

	// Allocate and copy the data
	if (vertexBuffer->dynamic)
		qglBufferData(GL_ARRAY_BUFFER, size, data, GL_STREAM_DRAW);
	else
		qglBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);

	// Link
	r_freeVertexBuffer = r_freeVertexBuffer->next;

	vertexBuffer->prev = &r_activeVertexBuffer;
	vertexBuffer->next = r_activeVertexBuffer.next;

	r_activeVertexBuffer.next->prev = vertexBuffer;
	r_activeVertexBuffer.next = vertexBuffer;

	return vertexBuffer;
}

/*
 ==================
 R_FreeVertexBuffer
 ==================
*/
void R_FreeVertexBuffer (vertexBuffer_t *vertexBuffer){

	if (!vertexBuffer)
		Com_Error(false, "R_FreeVertexBuffer: NULL vertexBuffer");

	// Delete the buffer
	qglDeleteBuffers(1, &vertexBuffer->bufferId);

	// Unlink
	vertexBuffer->prev->next = vertexBuffer->next;
	vertexBuffer->next->prev = vertexBuffer->prev;

	vertexBuffer->next = r_freeVertexBuffer;
	r_freeVertexBuffer = vertexBuffer;
}

/*
 ==================
 R_UpdateVertexBuffer
 ==================
*/
void R_UpdateVertexBuffer (vertexBuffer_t *vertexBuffer, const void *data, int size, int offset){

	if (!vertexBuffer)
		Com_Error(false, "R_UpdateVertexBuffer: NULL vertexBuffer");

	GL_BindVertexBuffer(vertexBuffer);

	qglBufferSubData(GL_ARRAY_BUFFER, offset, size, data);
}

/*
 ==================
 R_MapVertexBuffer
 ==================
*/
void *R_MapVertexBuffer (vertexBuffer_t *vertexBuffer){

	void	*map;

	if (!vertexBuffer)
		Com_Error(false, "R_MapVertexBuffer: NULL vertexBuffer");

	GL_BindVertexBuffer(vertexBuffer);

	map = qglMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
	if (!map)
		Com_DPrintf(S_COLOR_RED "R_MapVertexBuffer: couldn't map buffer\n");

	return map;
}

/*
 ==================
 R_UnmapVertexBuffer
 ==================
*/
void R_UnmapVertexBuffer (vertexBuffer_t *vertexBuffer){

	if (!vertexBuffer)
		Com_Error(false, "R_UnmapVertexBuffer: NULL vertexBuffer");

	GL_BindVertexBuffer(vertexBuffer);

	if (!qglUnmapBuffer(GL_ARRAY_BUFFER))
		Com_DPrintf(S_COLOR_RED "R_UnmapVertexBuffer: couldn't unmap buffer\n");
}

/*
 ==================
 R_ListVertexBuffers_f
 ==================
*/
static void R_ListVertexBuffers_f (void){

	vertexBuffer_t	*vertexBuffer;
	int				totalCount = 0, staticCount = 0, dynamicCount = 0;
	int				totalBytes = 0, staticBytes = 0, dynamicBytes = 0;

	if (!r_vertexBuffers->integerValue){
		Com_Printf("Vertex buffers are disabled\n");
		return;
	}

	for (vertexBuffer = r_activeVertexBuffer.next; vertexBuffer != &r_activeVertexBuffer; vertexBuffer = vertexBuffer->next){
		totalCount++;
		totalBytes += vertexBuffer->size;

		if (vertexBuffer->dynamic){
			dynamicCount++;
			dynamicBytes += vertexBuffer->size;
		}
		else {
			staticCount++;
			staticBytes += vertexBuffer->size;
		}
	}

	Com_Printf("\n");
	Com_Printf("%5i KB in %4i static buffers\n", staticBytes >> 10, staticCount);
	Com_Printf("%5i KB in %4i dynamic buffers\n", dynamicBytes >> 10, dynamicCount);
	Com_Printf("--------------------------------\n");
	Com_Printf("%.2f MB in %i vertex buffers\n", totalBytes * (1.0f/1048576), totalCount);
	Com_Printf("%i free vertex buffers\n", MAX_VERTEX_BUFFERS - totalCount);
	Com_Printf("\n");
}

/*
 ==================
 R_InitVertexBuffers
 ==================
*/
void R_InitVertexBuffers (void){

	int		i;

	// Register our commands
	Cmd_AddCommand("listVertexBuffers", R_ListVertexBuffers_f, "Lists vertex buffers");

	if (!r_vertexBuffers->integerValue)
		return;		// Not using vertex buffers

	// Allocate and set up the vertex buffers
	r_vertexBuffersList = (vertexBuffer_t *)Mem_ClearedAlloc(MAX_VERTEX_BUFFERS * sizeof(vertexBuffer_t), TAG_RENDERER);

	for (i = 0; i < MAX_VERTEX_BUFFERS - 1; i++)
		r_vertexBuffersList[i].next = &r_vertexBuffersList[i+1];

	r_activeVertexBuffer.prev = &r_activeVertexBuffer;
	r_activeVertexBuffer.next = &r_activeVertexBuffer;

	r_freeVertexBuffer = r_vertexBuffersList;
}

/*
 ==================
 R_ShutdownVertexBuffers
 ==================
*/
void R_ShutdownVertexBuffers (void){

	vertexBuffer_t	*vertexBuffer;

	// Unregister our commands
	Cmd_RemoveCommand("listVertexBuffers");

	if (!r_vertexBuffers->integerValue)
		return;		// Not using vertex buffers

	// Delete all vertex buffers
	qglBindBuffer(GL_ARRAY_BUFFER, 0);

	for (vertexBuffer = r_activeVertexBuffer.next; vertexBuffer != &r_activeVertexBuffer; vertexBuffer = vertexBuffer->next)
		qglDeleteBuffers(1, &vertexBuffer->bufferId);

	// Clear vertex buffer list
	r_activeVertexBuffer.prev = &r_activeVertexBuffer;
	r_activeVertexBuffer.next = &r_activeVertexBuffer;

	r_freeVertexBuffer = NULL;
}
